#include "Mesh.h"
#include "AABB.h"
#include "ray.h"
#include "result.h"

#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>

#include "Exception.h"

namespace mray{

inline void operator << (vec3& v1, const aiVector3D& v2){
	v1.x = v2.x;
	v1.y = v2.y;
	v1.z = v2.z;
}

inline void operator << (vec2& v1, const aiVector3D& v2){
	v1.x = v2.x;
	v1.y = v2.y;
}

}

mray::Mesh::Mesh(const std::string& filePath, bool regenNormal, BaseMaterial* pMtl, bool dSides) :
	BaseObject(pMtl, dSides)
{
	LoadFile(filePath, regenNormal);
}

void mray::Mesh::LoadFile(const std::string& filePath, bool regenNormal)
{
	//init with assimp
	Assimp::Importer importer;
	auto scene = importer.ReadFile(filePath,
		aiProcess_FlipUVs|
		aiProcess_FlipWindingOrder|
		aiProcess_OptimizeMeshes|
		aiProcess_ImproveCacheLocality|
		aiProcess_Triangulate|
		aiProcess_SortByPType|
		(regenNormal?aiProcess_GenNormals:0)|
		0
	);

	if(!scene) printf("Err with loading file:%s\n", filePath.c_str());

	//only process a mesh
	switch(scene->mNumMeshes){
	case 0:
		HandleError("Model has no mesh");
		break;
	case 1:
		break;
	default:
		HandleWarning("Warning only capable with modle with one mesh");
		break;
	}
	auto mesh = scene->mMeshes[0];

	m_indices.resize(mesh->mNumFaces * 3);
	m_verts.resize(mesh->mNumVertices);

	//copy data
	int globalIndex = 0;
	int globalVertex = 0;

	for(uint f = 0; f < mesh->mNumFaces; f++){
		auto& face = mesh->mFaces[f];

		if(face.mNumIndices != 3)
			HandleError("more than 3 vertices in a face");

		for(uint i = 0; i < face.mNumIndices; i++){
			uint index = face.mIndices[i];
			m_indices[globalIndex++] = index;
		}
	}
	//copy vertex
	if(mesh->HasPositions())	
		for(uint v = 0; v < mesh->mNumVertices; v++)
			m_verts[v].v << mesh->mVertices[v];
	else
		HandleError("Model has no vertex data");
	//copy normal
	if(mesh->HasNormals())
		for(uint v = 0; v < mesh->mNumVertices; v++)
			m_verts[v].n << mesh->mNormals[v];
	else
		for(uint v = 0; v < mesh->mNumVertices; v++)
			m_verts[v].n.x = m_verts[v].n.y = m_verts[v].n.z = 0.f;
	//copy texcoord
	if(mesh->HasTextureCoords(0))
		for(uint v = 0; v < mesh->mNumVertices; v++)
			m_verts[v].t << mesh->mTextureCoords[0][v];
	else
		for(uint v = 0; v < mesh->mNumVertices; v++)
			m_verts[v].t.u = m_verts[v].t.v = 0.f;

	//translate vertices to triangles
	m_triangles.resize(mesh->mNumFaces);

	uint* globalPtr = &m_indices[0];

	for(int t = 0; t < m_triangles.size(); t++){
		// check point
		m_triangles[t].m_ptr	= globalPtr;
		m_triangles[t].m_pverts	= &m_verts;
		m_triangles[t].m_pmesh	= this;
		globalPtr += 3;
	}

}

bool mray::Triangle::Hit(Ray& ray, Result* pResult) const
{
	const vec3& p0 = (*m_pverts)[m_ptr[0]].v;
	const vec3& p1 = (*m_pverts)[m_ptr[1]].v;
	const vec3& p2 = (*m_pverts)[m_ptr[2]].v;

	const vec3 e1 = p1 - p0;
	const vec3 e2 = p2 - p0;
	vec3 s = ray.m_ori - p0;

	float coords[3] = {0.f, 0.f, 0.f};

	float invSu = 1.f / ((e1 % e2) * (-ray.m_dir));
	if(invSu == 0.f) return false;
	coords[1] = (s % e2) * (-ray.m_dir) * invSu;
	if(coords[1] < 0.f || coords[1] > 1.f) return false;
	coords[2] = (e1 % s) * (-ray.m_dir) * invSu;
	if(coords[2] < 0.f || coords[2] > 1.f) return false;
	if(coords[1] + coords[2] > 1.f) return false;
	float t	 = (e1 % e2) * s * invSu;
	if(t < 0.f) return false;
	 
	if(ray.m_mint > t){
		ray.m_mint = t;
		if(pResult){
			pResult->pMtl = m_pmesh->m_pMtl;
			pResult->p = ray.m_ori + ray.m_dir * t;
			coords[0] = 1.f-coords[1]-coords[2];
			for(uint i = 0; i < 3; i++){
				pResult->n += (*m_pverts)[m_ptr[i]].n * coords[i];
				pResult->t += (*m_pverts)[m_ptr[i]].t * coords[i];
			}
			pResult->d = ray.m_dir;
		}
	}

	return true;
}

bool mray::Triangle::GetAABB(AABB& aabb) const
{
	aabb.Reset();
	for(uint i = 0; i < 3; i++)
		aabb &= (*m_pverts)[m_ptr[i]].v;
	return true;
}